package com.kunpan.admin.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.github.pagehelper.PageInfo;
import com.kunpan.admin.aop.AutoFillAspect;
import com.kunpan.admin.async.MinioFileUploadAsyncService;
import com.kunpan.admin.common.Enum.BusinessType;
import com.kunpan.admin.common.Enum.FileStatusEnum;
import com.kunpan.admin.common.Enum.FileTypeEnum;
import com.kunpan.admin.common.domain.AjaxResult;
import com.kunpan.admin.common.redis.RedisCache;
import com.kunpan.admin.common.redis.RedisMessagePublisher;
import com.kunpan.admin.common.utils.file.FileUtils;
import com.kunpan.admin.common.utils.file.MinioUtils;
import com.kunpan.admin.constant.Constants;
import com.kunpan.admin.constant.RedisMessage;
import com.kunpan.admin.entity.ChunkFile;
import com.kunpan.admin.entity.PanFile;
import com.kunpan.admin.entity.User;
import com.kunpan.admin.entity.dto.MessageDto;
import com.kunpan.admin.entity.dto.PanFileDto;
import com.kunpan.admin.exception.file.MinIOFileStorageException;
import com.kunpan.admin.mapper.PanFileMapper;
import com.kunpan.admin.mapper.UserMapper;
import com.kunpan.admin.service.PanFileService;
import com.kunpan.admin.service.TransactionService;
import io.minio.messages.DeleteObject;
import io.swagger.models.auth.In;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.kunpan.admin.constant.RedisMessage.DELETE_CACHE_KEY;

@Service
public class PanFileServiceImpl implements PanFileService {


    private static final Logger log = LoggerFactory.getLogger(AutoFillAspect.class);

    @Autowired
    private PanFileMapper panFileMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private MinioUtils minioUtils;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private RedisMessagePublisher redisMessagePublisher;

    @Autowired
    private MinioFileUploadAsyncService minioFileUploadAsyncService;

    @Autowired
    private TransactionService transactionService; // 注入事务Service

    @Override
    public AjaxResult getFileList(PanFileDto panFileDto) {

        Integer userId = Integer.valueOf(StpUtil.getLoginId().toString());
        panFileDto.setUserId(userId);
        if (panFileDto.getFileType() != null && panFileDto.getFileType() > 0) {
            panFileDto.setParentId(null);
        }

        //判断是不是根据文件名进行模糊查询
        if (panFileDto.getFileName() != null) {
            return handleFolderFilesAndFuzzyQuery(panFileDto);
        }

        // 判断是否查询文件夹内部文件
        if (panFileDto.getParentId() != null && panFileDto.getParentId() > 0) {
            return handleFolderFilesAndFuzzyQuery(panFileDto);
        }
        return handleNonFolderFiles(panFileDto, userId);
    }


    /**
     * 处理文件夹内部文件查询以及模糊查询
     */
    private AjaxResult handleFolderFilesAndFuzzyQuery(PanFileDto panFileDto) {
        List<PanFile> fileList = panFileMapper.selectFileList(panFileDto);
        PageInfo<PanFile> pageInfo = new PageInfo<>(fileList);
        // 返回结果
        Map<String, Object> filesMap = new HashMap<>();
        //将数据分别组装起来
        filesMap.put("total", pageInfo.getTotal());
        filesMap.put("list", pageInfo.getList());
        return AjaxResult.success(filesMap);
    }


    /**
     * 处理非文件夹内部文件查询
     */
    private AjaxResult handleNonFolderFiles(PanFileDto panFileDto, Integer userId) {
        // 1. 构建缓存键
        String totalKey = String.format(Constants.FILE_LIST_TOTAL_CACHE_KEY, userId, panFileDto.getFileType());
        String pageCacheKey = String.format(Constants.FILE_PAGE_LIST_CACHE_KEY,
                userId, panFileDto.getFileType(), panFileDto.getPageNum(), panFileDto.getPageSize());
        // 2. 尝试从缓存获取分页数据
        List<PanFile> cachedList = redisCache.getCacheObject(pageCacheKey);
        Long total = redisCache.getCacheObject(totalKey);
        // 返回结果
        Map<String, Object> filesMap = new HashMap<>();

        if (cachedList != null && total != null) {
            // 缓存命中，直接返回
            PageInfo<PanFile> pageInfo = new PageInfo<>(cachedList);
            filesMap.put("total", total);
            filesMap.put("list", pageInfo.getList());
            return AjaxResult.success(filesMap);
        }

        cachedList = panFileMapper.selectFileList(panFileDto);
        // 使用 PageInfo 获取分页信息
        PageInfo<PanFile> pageInfo = new PageInfo<>(cachedList);

        // 4. 重建缓存
        if (!cachedList.isEmpty()) {
            // 4.1 缓存总记录数
            redisCache.setCacheObject(totalKey, pageInfo.getTotal(), 10, TimeUnit.HOURS);
            // 4.2 缓存文件ID到有序集合，并设置过期时间
//            Map<String, Double> idScores = new HashMap<>();
//            for (PanFile file : cachedList) {
//                String member = "file:" + file.getId();
//                idScores.put(member, (double) file.getCreateTime().getTime());
//                // 缓存文件详情到Hash
//                redisCache.setCacheObject("file:details:" + file.getId(), file, 10, TimeUnit.HOURS);
//            }
//            redisTemplate.opsForZSet().add(sortedSetKey, idScores);
//            redisCache.expire(sortedSetKey, 10, TimeUnit.HOURS);

            // 4.3 缓存当前分页数据（避免频繁范围查询）
            redisCache.setCacheObject(pageCacheKey, cachedList, 1, TimeUnit.HOURS);
        }
        //将数据分别组装起来
        filesMap.put("total", pageInfo.getTotal());
        filesMap.put("list", pageInfo.getList());
        return AjaxResult.success(filesMap);
    }

    /**
     * 新建文件夹
     */
    @Override
    public AjaxResult addFolder(PanFile panFile) {
        panFile.setUserId(Integer.valueOf(StpUtil.getLoginId().toString()));
        if (panFile.getParentId() == null) {
            panFile.setParentId(0);
        }
        PanFileDto panFileDto = new PanFileDto();
        BeanUtils.copyProperties(panFile, panFileDto);
        //判断相同目录下是否有同名文件
        if (!panFileMapper.selectFileList(panFileDto).isEmpty()) {
            //重命名文件
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSS");
            String newFileName = panFile.getFileName() + "_" + dateFormat.format(System.currentTimeMillis());
            panFile.setFileName(newFileName);
        }
        if (panFileMapper.insertFile(panFile) > 0) {
            //删除缓存数据
            String key = String.format(Constants.FILE_TYPE_LIST_CACHE_KEY, Integer.valueOf(StpUtil.getLoginId().toString()), null);
            redisCache.deleteKeysByPattern(key);
            return AjaxResult.success();
        }
        return AjaxResult.error("新增失败，请检查网络连接");
    }


    /**
     * 秒传判断与处理（优化点：逻辑分层+异常处理）
     */
    @Override
    public AjaxResult chuckFileMd5Upload(Long fileSize, Integer parentId, String fileMd5, String fileName) {
        try {
            int fileStatus = 0;
            Integer userId = Integer.valueOf(StpUtil.getLoginId().toString());
            // 分层查询：缓存->数据库（优化查询逻辑）
            String chuckKey = String.format(Constants.CHUCK_FILEMD5, fileMd5);
            PanFile existFile = getExistFileFromCacheOrDB(fileMd5, userId, chuckKey);
            if (existFile != null && transactionService.handleFastUpload(existFile, parentId, fileSize, fileName, userId)) {
                redisCache.setCacheMapValue(chuckKey, userId.toString(), existFile);
                //设置缓存5分钟
                redisCache.expire(chuckKey, 5, TimeUnit.MINUTES);
                //发布清除对应缓存的消息
                MessageDto messageDto = MessageDto.create(userId, existFile.getFileType(), fileSize, BusinessType.UPLOAD.toString());
                redisMessagePublisher.publishFileUploadEvent(messageDto);
                fileStatus = 1;
                return AjaxResult.success("秒传成功", fileStatus);
            }
            return AjaxResult.success("无法秒传", fileStatus);
        } catch (Exception e) {
            log.error("秒传处理异常 | md5:{}", fileMd5, e);
            return AjaxResult.error("系统繁忙，请稍后重试");
        }
    }

    /**
     * 分层查询文件存在性
     */
    private PanFile getExistFileFromCacheOrDB(String fileMd5, Integer userId, String chuckKey) {
        PanFile panFile = redisCache.getCacheMapValue(chuckKey, userId.toString());
        return (panFile != null) ? panFile : checkFileMd5(fileMd5);
    }

    /**
     * 检查MD5相同文件
     */
    public PanFile checkFileMd5(String fileMd5) {
        return panFileMapper.selectFileListByFileMd5AndFileSize(fileMd5)
                .stream()
                .findFirst()
                .orElse(null);  // 明确返回null更安全
    }

    /**
     * 小文件上传
     */
    @Override
    @Transactional
    public AjaxResult fileUpload(MultipartFile file, Integer parentId, String fileMd5) {
        //查询文件是否已经上传，已经上传直接return
        if (redisCache.getStatus(fileMd5, 1)) {
            return AjaxResult.success();
        }
        PanFile panFile = new PanFile();
        //先确定文件类型
        panFile.setFileType(FileUtils.getMultipartFileType(file.getOriginalFilename()));
        panFile.setFileSize(file.getSize());
        //去除非法字符
        String fileName = Objects.requireNonNull(file.getOriginalFilename()).replace("[/\\\\:*?\"<>|]", "");
        panFile.setFileName(fileName);
        panFile.setFileMd5(fileMd5);
        panFile.setParentId(parentId);
        panFile.setUserId(Integer.valueOf(StpUtil.getLoginId().toString()));
        //设置属性
        panFile.setFileStatus(FileStatusEnum.NORMAL.getValue());
        Date now = new Date();
        panFile.setCreateTime(now);
        panFile.setUpdateTime(now);
//            FileUtils.fileUpload(file, file.getOriginalFilename());
        //执行上传操作
        String filePath = minioUtils.minioUpload(file, fileName, fileMd5);
        panFile.setFilePath(filePath);
        //修改当前父文件的状态
        if (parentId != 0) {
            PanFile updateTimeParent = new PanFile();
            updateTimeParent.setId(parentId);
            updateTimeParent.setUpdateTime(now);
            panFileMapper.updateFile(updateTimeParent);
        }
        if (panFileMapper.insertFile(panFile) > 0) {
            User user = userMapper.selectUserById(Integer.valueOf(StpUtil.getLoginId().toString()));
            User user1 = new User();
            user1.setUserId(user.getUserId());
            user1.setUseSpace(user.getUseSpace() + file.getSize());
            if (userMapper.updateUserInfo(user1) > 0) {
                //发布清除对应缓存的消息
                MessageDto messageDto = MessageDto.create(user.getUserId(), panFile.getFileType(), file.getSize(), BusinessType.UPLOAD.toString());
                redisMessagePublisher.publishFileUploadEvent(messageDto);
                return AjaxResult.success("上传成功");
            } else {
                return AjaxResult.error("上传失败，请稍后重试");
            }
        }
        return AjaxResult.error("上传失败,请检查网络连接");
    }

    /**
     * 分片上传
     */
    @Override
    public AjaxResult uploadFileChunk(MultipartFile file, ChunkFile chunkFile) {
        try {
            String userId = StpUtil.getLoginId().toString();
            InputStream fileInputStream = file.getInputStream();
            //异步的进行文件分片上传
            minioFileUploadAsyncService.minioChunkUpload(fileInputStream, chunkFile, userId);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return AjaxResult.success("分片上传成功");
    }

    /**
     * 取消上传
     */
    @Override
    public AjaxResult cancelFileUpload(String fileMd5) {
        minioFileUploadAsyncService.deleteChuckFile(fileMd5);
        return AjaxResult.success();
    }

    /**
     * 合并分片上传操作
     */
    @Transactional
    @Override
    public AjaxResult mergeChunkFile(String fileMd5, Integer parentId, Integer shardTotal, String fileName) {
        PanFile panFile = new PanFile();
        try {
            //合并分片
            String key = fileMd5 + "_" + StpUtil.getLoginId().toString();
            Long fileSize = minioUtils.getFileSize(key);
            minioUtils.deleteFileSize(key);
            String filePath = minioUtils.mergeChunks(fileMd5, shardTotal, fileName);
            // 设置合并后的文件信息
            panFile.setFileStatus(FileStatusEnum.NORMAL.getValue());
            Date now = new Date();
            panFile.setCreateTime(now);
            panFile.setUpdateTime(now);
            panFile.setUserId(Integer.valueOf(StpUtil.getLoginId().toString()));
            panFile.setFileType(FileUtils.getMultipartFileType(fileName));
            panFile.setFileMd5(fileMd5);
            panFile.setParentId(parentId);
            panFile.setFileSize(fileSize);
            panFile.setFileName(fileName);
            panFile.setFilePath(filePath);
            // 插入数据库记录
            if (panFileMapper.insertFile(panFile) > 0) {
                // 更新用户空间
                User user = userMapper.selectUserById(Integer.valueOf(StpUtil.getLoginId().toString()));
                User user1 = new User();
                user1.setUserId(user.getUserId());
                user1.setUseSpace(user.getUseSpace() + fileSize);
                if (userMapper.updateUserInfo(user1) > 0) {
                    return AjaxResult.success("文件上传并合并成功");
                } else {
                    return AjaxResult.error("上传失败，请稍后重试");
                }
            } else {
                return AjaxResult.error("上传失败，请检查网络连接");
            }
        } catch (Exception e) {
            return AjaxResult.error("上传失败，请检查网络连接");
        }
    }

    /**
     * 文件下载，获取文件预签名URL
     */
    @Override
    public AjaxResult fileDownload(String objectName, Long fileSize, HttpServletResponse response) {
        String url;
        url = minioUtils.generatePresignedUrl(objectName, fileSize);
        return AjaxResult.success("获取成功", url);
    }


    //        List<PanFile> panFileList;
//        //判断当前是不是查询全部文件(这些文件都会包含目录)，目录是是树形结构
//        if (panFileDto.getFileType() == null) {
//            //完善数据的树性结构
//            panFileList = buildTree(list);
//        } else {
//            panFileList = list;
//        }

    /**
     * 构造树形结构数据
     */
    public List<PanFile> buildTree(List<PanFile> fileList) {
        Map<Integer, PanFile> fileMap = new HashMap<>();
        List<PanFile> roots = new ArrayList<>();
        for (PanFile file : fileList) {
            if (file.getParentId() == null || file.getParentId() == 0) {
                roots.add(file);
            }
            fileMap.put(file.getId(), file);
        }
        //判断根节点roots是否有数据,如果没有，说明当前不是树性结构，fileList全为子节点数据，提前退出此方法
        if (roots.isEmpty()) {
            return fileList;
        }
        for (PanFile file : fileList) {
            if (file.getParentId() != null) {//判断是否存在父节点
                //将当前数据添加到父节点上
                PanFile parent = fileMap.get(file.getParentId());
                if (parent != null) {
                    //将所有子文件的大小加起来就是父文件的总大小
//                    Long size = (parent.getFileSize() == null ? 0L : parent.getFileSize()) + (file.getFileSize() == null ? 0L : file.getFileSize());
//                    parent.setFileSize(size);
                    parent.getChildren().add(file);
                }
            }
        }
        return roots;
    }
}
